﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EmitMapper;
using EmitMapper.MappingConfiguration;
using System.Text;
using System.Reflection;

namespace HigLabo.Core
{
    public static class ObjectMapper
    {
        private static Object _LockObject = new object();
        private static List<CustomDefaultMapConfig> _MapConfigrations = new List<CustomDefaultMapConfig>();
        private static ObjectMapperManager _Manager = new ObjectMapperManager();

        public static void ClearMap<T, TResult>()
        {
            CustomDefaultMapConfig config = AddCustomDefaultMapConfig(typeof(T), typeof(TResult));
            _MapConfigrations.Remove(config);
        }
        public static void AddConstructor<T, TResult>(Func<TResult> constructor)
        {
            CustomDefaultMapConfig config = AddCustomDefaultMapConfig(typeof(T), typeof(TResult));
            config.ConstructBy(() => constructor());
            config.LastUpdateTime = DateTime.Now;
        }
        public static void AddTypeConverter<T, TResult, From, To>(Func<From, To> converter)
        {
            CustomDefaultMapConfig config = AddCustomDefaultMapConfig(typeof(T), typeof(TResult));
            config.ConvertUsing(converter);
            config.LastUpdateTime = DateTime.Now;
        }
        public static void SetIgnoreMembers<T, TResult>(params String[] memberNames)
        {
            CustomDefaultMapConfig config = AddCustomDefaultMapConfig(typeof(T), typeof(TResult));
            config.IgnoreMembers<T, TResult>(memberNames);
            config.LastUpdateTime = DateTime.Now;
        }
        public static void AddAction<T, TResult>(Action<T, TResult> action)
        {
            CustomDefaultMapConfig config = AddCustomDefaultMapConfig(typeof(T), typeof(TResult));
            config.Actions.Add(action);
        }

        public static TResult Map<T, TResult>(this T source)
        {
            CustomDefaultMapConfig config = GetCustomDefaultMapConfig(typeof(T), typeof(TResult));

            if (config == null)
            {
                return _Manager.GetMapper<T, TResult>().Map(source);
            }
            else
            {
                var target = _Manager.GetMapper<T, TResult>(config).Map(source);

                config.OnAction(source, target);
                return target;
            }
        }
        public static TResult Map<T, TResult>(this T source, TResult target)
        {
            CustomDefaultMapConfig config = GetCustomDefaultMapConfig(typeof(T), typeof(TResult));

            if (config == null)
            {
                _Manager.GetMapper<T, TResult>().Map(source, target);
            }
            else
            {
                _Manager.GetMapper<T, TResult>(config).Map(source, target);
                config.OnAction(source, target);
            }
            return target;
        }
        public static IEnumerable<TResult> Map<T, TResult>(this IEnumerable<T> source, Func<TResult> func)
        {
            CustomDefaultMapConfig config = GetCustomDefaultMapConfig(typeof(T), typeof(TResult));
            ObjectsMapper<T, TResult> mapper = null;

            if (config == null)
            {
                mapper = _Manager.GetMapper<T, TResult>();
            }
            else
            {
                mapper = _Manager.GetMapper<T, TResult>(config);
            }

            foreach (var item in source)
            {
                var target = func();
                var result = mapper.Map(item, target);
                if (config != null)
                {
                    config.OnAction(source, target);
                }
                yield return result;
            }
        }
   
        public static T Map<T>(this Dictionary<String, String> source)
           where T : class, new()
        {
            return Map(source, new T());
        }
        public static T Map<T>(this Dictionary<String, String> source, T target)
        {
            return Map(source.ToDictionary(el => el.Key, el => el.Value as Object), target);
        }
        public static T Map<T>(this Dictionary<String, Object> source)
           where T : class, new()
        {
            return Map(source, new T());
        }
        public static T Map<T>(this Dictionary<String, Object> source, T target)
        {
            Type someObjectType = target.GetType();

            foreach (KeyValuePair<string, object> item in source)
            {
                someObjectType.GetProperty(item.Key).SetValue(target, item.Value, null);
            }
            return target;
        }
      
        public static Dictionary<String, Object> Map(this Object source)
        {
            return Map(source, new Dictionary<string, object>(), BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
        }
        public static Dictionary<String, String> Map(this Object source, Dictionary<String, String> target)
        {
            return Map(source, target, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
        }
        public static Dictionary<String, String> Map(this Object source, Dictionary<String, String> target, BindingFlags bindings)
        {
            Dictionary<string, string> d = new Dictionary<string, string>();
            foreach (var item in Map(source))
            {
                if (item.Value == null)
                {
                    d[item.Key] = null;
                }
                else
                {
                    d[item.Key] = item.Value.ToString();
                }
            }
            return d;
        }
        public static Dictionary<String, Object> Map(this Object source, Dictionary<String, Object> target, BindingFlags bindings)
        {
            foreach (var item in source.GetType().GetProperties(bindings))
            {
                target[item.Name] = item.GetValue(source, null);
            }
            return target;
        }

        private static CustomDefaultMapConfig AddCustomDefaultMapConfig(Type sourceType, Type targetType)
        {
            CustomDefaultMapConfig config = null;
            lock (_LockObject)
            {
                config = _MapConfigrations.Find(el => el.SourceType == sourceType && el.TargetType == targetType);
                if (config == null)
                {
                    config = new CustomDefaultMapConfig(sourceType, targetType);
                    _MapConfigrations.Add(config);
                }
            }
            return config;
        }
        private static CustomDefaultMapConfig GetCustomDefaultMapConfig(Type sourceType, Type targetType)
        {
            var config = _MapConfigrations.Find(el =>
                sourceType == el.SourceType && targetType == el.TargetType);
            if (config != null) { return config; }

            return _MapConfigrations.Find(el => 
                sourceType.IsInheritanceFrom(el.SourceType) && targetType.IsInheritanceFrom(el.TargetType));
        }

        private class CustomDefaultMapConfig : DefaultMapConfig
        {
            public Type SourceType { get; set; }
            public Type TargetType { get; set; }
            public DateTime LastUpdateTime { get; set; }
            public List<Delegate> Actions { get; private set; }
            public CustomDefaultMapConfig(Type sourceType, Type targetType)
            {
                this.SourceType = sourceType;
                this.TargetType = targetType;
                this.Actions = new List<Delegate>();
            }
            public override string GetConfigurationName()
            {
                return base.GetConfigurationName() + this.LastUpdateTime.Ticks.ToString();
            }
            public void OnAction<T, TResult>(T source, TResult target)
            {
                foreach (Delegate item in this.Actions)
                {
                    var action = item as Action<T, TResult>;
                    if (action != null)
                    {
                        action(source, target);
                    }
                }
            }
        }
    }
}